package org.lex.jdbc.url.persist.xml;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.lex.io.utils.FileUtils;
import org.lex.jdbc.url.CommonObject;

public class XmlDaoHelper<T extends CommonObject> implements XmlDaoConstants {

	public List<T> getAllEntities(File xml, XmlEntityDao<T> dao)
			throws IOException {
		Document doc = this.read(xml);
		List<T> results = new ArrayList<T>();
		for (Iterator<Element> driverElementIter = dao.getEntityIterator(doc); driverElementIter
				.hasNext();) {
			Element entityElement = driverElementIter.next();
			results.add(dao.unmarshal(entityElement));
		}
		return results;
	}

	public void save(File xml, XmlEntityDao<T> dao, T driver)
			throws IOException {
		Document doc = this.read(xml);
		this.checkEntityNotExists(doc, dao, driver);
		dao.marshal(driver, dao.createNewEntityElement(doc));
		this.save(doc, xml);
	}

	public void update(File file, XmlEntityDao<T> dao, T oldEntity, T newEntity)
			throws IOException {
		String oldEntityName = oldEntity.getName();
		Document doc = this.read(file);
		Element oldElement = this.findEntityElementByObjectName(doc, dao,
				oldEntityName);
		if (null == oldElement) {
			throw new IOException("Old entity ["
					+ oldEntity.getClass().getSimpleName() + "] not exists: "
					+ oldEntityName);
		}
		oldElement = this.removeAllChildren(oldElement);
		dao.marshal(newEntity, oldElement);
		this.save(doc, file);
	}

	public void delete(File xmlFile, XmlEntityDao<T> dao, T entity)
			throws IOException {
		String entityName = entity.getName();
		Document doc = this.read(xmlFile);
		Element element = this.findEntityElementByObjectName(doc, dao,
				entityName);
		if (null == element) {
			throw new IOException("Old entity["
					+ entity.getClass().getSimpleName() + "] not exists: "
					+ entityName);
		}
		element.getParent().remove(element);
		this.save(doc, xmlFile);
	}

	public void checkEntityNotExists(Document doc, XmlEntityDao<T> dao, T entity)
			throws IOException {
		String name = entity.getName();
		if (null != this.findEntityElementByObjectName(doc, dao, name)) {
			throw new IOException(entity.getClass().getSimpleName() + " name["
					+ name + "] already exists");
		}
	}

	public Element findEntityElementByObjectName(Document doc,
			XmlEntityDao<T> dao, String objectName) {
		Element result = null;
		for (Iterator<Element> entityElementIterator = dao
				.getEntityIterator(doc); entityElementIterator.hasNext();) {
			Element e = entityElementIterator.next();
			if (e.attributeValue("name").equals(objectName)) {
				result = e;
				break;
			}
		}
		return result;
	}

	public Element removeAllChildren(Element element) {
		String elementName = element.getName();
		Element parent = element.getParent();
		parent.remove(element);
		element = parent.addElement(elementName);
		return element;
	}

	public void save(Document doc, File file)
			throws UnsupportedEncodingException, IOException {
		OutputStreamWriter out = null;
		try {
			out = new OutputStreamWriter(new BufferedOutputStream(
					FileUtils.openFile(file), BUF_SIZE), CHARSET);
			OutputFormat of = FORMAT_XML ? OutputFormat.createPrettyPrint()
					: OutputFormat.createCompactFormat();
			XMLWriter xmlOut = new XMLWriter(out, of);
			xmlOut.write(doc);
			xmlOut.flush();
		} finally {
			if (null != out) {
				out.flush();
				out.close();
			}
		}
	}

	public Document read(File file) throws UnsupportedEncodingException,
			IOException {
		Document doc = null;
		if (false == file.exists() || false == file.isFile()
				|| false == file.canRead()) {
			doc = DocumentHelper.createDocument();
			doc.addElement("Drivers");
			doc.setXMLEncoding(CHARSET);
		} else {
			InputStreamReader in = null;
			try {
				in = new InputStreamReader(new BufferedInputStream(
						new FileInputStream(file), BUF_SIZE), CHARSET);
				doc = new SAXReader().read(in);
			} catch (DocumentException e) {
				throw new IOException("parse resource [" + file
						+ "] failed, not a valid xml document", e);
			} finally {
				if (null != in) {
					in.close();
				}
			}
		}
		return doc;
	}

}
